package my.software;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import my.software.basic.Point3d;
import my.software.basic.Rectangle;
import my.software.utility.FloatUtil;
import processing.core.PApplet;
import au.com.bytecode.opencsv.CSV;
import au.com.bytecode.opencsv.CSVReader;

public class CityMap {
    
    private float accumulateNumber = 30;
    private float leftBorder = Float.MAX_VALUE;
    private float rightBorder = 0.0f;
    private float upBorder = 0.0f;
    private float downBorder = Float.MAX_VALUE;
    private String csvPath = "";
    private float waterHeight = 3.0f;
    
    private float yPrevious = Float.MAX_VALUE;
    private Integer skipLineNumber = 1; // ignore title line.
    private Map<Float, Integer> yCsvMap = new HashMap<Float, Integer>();
    
    private List<List<Point3d>> mapView; // graphic points.
    
    private Point3d centerPoint;
    private Rectangle viewInfo;
    
    public CityMap(Point3d centerPoint, float width, float height) {
        this.centerPoint = centerPoint;
        this.viewInfo = setSize(centerPoint, width, height);
        this.mapView = new ArrayList<List<Point3d>>();
    }

    public void setCsvPath(String csvPath) {
        this.csvPath = csvPath;
    }
    
    public float getKeyNearestAndSmallestOfKey(float key) {
        float currentKey = Float.MAX_VALUE;
        for (float mapKey : yCsvMap.keySet()) {
            if (mapKey < key && currentKey > mapKey) {
                currentKey = mapKey;
            }
        }
        return currentKey;
    }
    
    public void loadCsvData() throws IOException {
        if (this.csvPath.isEmpty()) {
            System.out.println("Please set csv path!");
        }
        mapView.clear();
        
        // read csv file
        if (centerPoint.getY() != 0.0f || centerPoint.getX() != 0.0f) {
            skipLineNumber = yCsvMap.get(viewInfo.getLeftBorder());
            if (skipLineNumber == null) {
//                skipLineNumber = yCsvMap.get(getKeyNearestAndSmallestOfKey(viewInfo.getLeftBorder()));
            }
        }
        if (skipLineNumber == null) {
            skipLineNumber = 1;
        }
        CSV csv = CSV.skipLines(skipLineNumber).create();
        
        CSVReader reader = csv.reader(this.csvPath);
        
        List<Point3d> rowList = new ArrayList<Point3d>();
        
        int csvRowIndex = 0;
        String[] values = reader.readNext();
        if (centerPoint.getY() == 0.0f && centerPoint.getX() == 0.0f) {
            centerPoint.setX(FloatUtil.parseFloat(values[0]));
            centerPoint.setY(FloatUtil.parseFloat(values[1]));
            leftBorder = centerPoint.getY();
            viewInfo = setSize(centerPoint, viewInfo.getWidth(), viewInfo.getHeight());
        }
        while (values != null) {
//            System.out.println(csvRowIndex + " | " + Arrays.asList(values));
            float x = FloatUtil.parseFloat(values[0]);
            float y = FloatUtil.parseFloat(values[1]);
            float z = FloatUtil.parseFloat(values[2]);
            if (y != yPrevious) {
                if (y == 446633.057678f) {
                    int d = 7;
                }
				List<Point3d> column = new ArrayList<Point3d>();
                for (Point3d realPoint : rowList) {
                    if (viewInfo.getUpBorder() <= realPoint.getX() && realPoint.getX() <= viewInfo.getDownBorder()) {
                        column.add(new Point3d(realPoint.getX(), realPoint.getY(), realPoint.getZ()));
                    }
                }
                if (column != null && !column.isEmpty()) {
                    mapView.add(column);
                }
                rowList = new ArrayList<Point3d>();
                Integer index = yCsvMap.get(y);
                if (index == null) {
                    yCsvMap.put(y, skipLineNumber + csvRowIndex);
                }
                yPrevious = y;
            }
            
            if (viewInfo.getLeftBorder() >= y && y >= viewInfo.getRightBorder()) {
                Point3d realPoint = new Point3d(x, y, z);
                rowList.add(realPoint);
            } else if (y < viewInfo.getRightBorder()) {
                break;
            }
            values = reader.readNext();
            csvRowIndex++;
        }
        reader.close();
        
        convertToDigitalPoints(mapView);
    }
    
    private void convertToDigitalPoints(List<List<Point3d>> mapView) {
        float realXTrans = Float.MAX_VALUE;
        float realYTrans = 0.0f;
        
        // get real transitions.
        for (int columnIndex = 0; columnIndex < mapView.size(); columnIndex++) {
            List<Point3d> columnList = mapView.get(columnIndex);
            for (int rowIndex = 0; rowIndex < columnList.size(); rowIndex++) {
                Point3d realPoint = columnList.get(rowIndex);
                if (realXTrans > realPoint.getX()) {
                    realXTrans = realPoint.getX();
                }
                if (realYTrans < realPoint.getY()) {
                    realYTrans = realPoint.getY();
                }
            }
        }
        
        for (int columnIndex = 0; columnIndex < mapView.size(); columnIndex++) {
            List<Point3d> columnList = mapView.get(columnIndex);
            for (int rowIndex = 0; rowIndex < columnList.size(); rowIndex++) {
                Point3d realPoint = columnList.get(rowIndex);
                columnList.set(rowIndex, new Point3d(realPoint, realXTrans, realYTrans));
            }
        }
    }
    
    public float lowestZ() {
        float lowest = Float.MAX_VALUE;
     // get real transitions.
        for (int columnIndex = 0; columnIndex < mapView.size(); columnIndex++) {
            List<Point3d> columnList = mapView.get(columnIndex);
            for (int rowIndex = 0; rowIndex < columnList.size(); rowIndex++) {
                Point3d realPoint = columnList.get(rowIndex);
                if (lowest > realPoint.getY()) {
                    lowest = realPoint.getY();
                }
            }
        }
        return lowest + accumulateNumber;
    }
    
    public float highestZ() {
        float highest = -Float.MAX_VALUE;
     // get real transitions.
        for (int columnIndex = 0; columnIndex < mapView.size(); columnIndex++) {
            List<Point3d> columnList = mapView.get(columnIndex);
            for (int rowIndex = 0; rowIndex < columnList.size(); rowIndex++) {
                Point3d realPoint = columnList.get(rowIndex);
                if (highest < realPoint.getY()) {
                    highest = realPoint.getY();
                }
            }
        }
        return highest + accumulateNumber;
    }
    
    public void play() {
        
    }
    
    public void pause() {
        
    }
    
    private void move(float x, float y, float z) throws IOException {
        centerPoint = new Point3d(x + (viewInfo.getWidth() / 2), y + (viewInfo.getHeight() / 2), z);
        viewInfo = setSize(centerPoint, viewInfo.getWidth(), viewInfo.getHeight());
        loadCsvData();
    }
    
    public void moveLeft(float moveSpeed) throws IOException {
//        if (viewInfo.getLeftTopPoint().getY() == leftBorder) {
//            return;
//        }
//        if (viewInfo.getLeftTopPoint().getY() + moveSpeed <= leftBorder) {
            // move
            move(this.viewInfo.getLeftTopPoint().getX(),
                    this.viewInfo.getLeftTopPoint().getY() + moveSpeed,
                    this.viewInfo.getLeftTopPoint().getZ());
//        } 
//        else {
//            move(this.viewInfo.getLeftTopPoint().getX(),
//                    leftBorder,
//                    this.viewInfo.getLeftTopPoint().getZ());
//        }
    }
    
    public void moveRight(float moveSpeed) throws IOException {
//        if (viewInfo.getLeftTopPoint().getY() + viewInfo.getWidth() - moveSpeed > rightBorder) {
            // move
            move(this.viewInfo.getLeftTopPoint().getX(),
                    this.viewInfo.getLeftTopPoint().getY() - moveSpeed,
                    this.viewInfo.getLeftTopPoint().getZ());
//        }
    }
    
    public void moveDown(float moveSpeed) throws IOException {
//        if (viewInfo.getLeftTopPoint().getX() + viewInfo.getHeight() + moveSpeed < downBorder) {
            // move
            move(this.viewInfo.getLeftTopPoint().getX() + moveSpeed,
                    this.viewInfo.getLeftTopPoint().getY(),
                    this.viewInfo.getLeftTopPoint().getZ());
//        }
    }
    
    public void moveUp(float moveSpeed) throws IOException {
//        if (viewInfo.getLeftTopPoint().getX() - moveSpeed > upBorder) {
            // move
            move(this.viewInfo.getLeftTopPoint().getX() - moveSpeed,
                    this.viewInfo.getLeftTopPoint().getY(),
                    this.viewInfo.getLeftTopPoint().getZ());
//        }
    }
    
    public void display(PApplet parent, float xTrans, float yTrans) {
        float pointWidth = 3;
        float pointHeight = 3;
        parent.stroke(255, 0, 0);
        parent.rect(xTrans - 1, yTrans - 1, this.viewInfo.getWidth() + pointWidth + 1, this.viewInfo.getHeight() + pointHeight + 1);
        parent.noStroke();
        for (int columnIndex = 0; columnIndex < mapView.size(); columnIndex++) {
            List<Point3d> columnList = mapView.get(columnIndex);
            for (int rowIndex = 0; rowIndex < columnList.size(); rowIndex++) {
                Point3d digitalPoint = columnList.get(rowIndex);
                parent.fill(255);
                if (waterHeight > digitalPoint.getY()) {
                    parent.fill(0, 0, 255);
                } else {
                    
                    parent.fill((accumulateNumber + digitalPoint.getY()) * (float)(255 / (10 + accumulateNumber)));
                }
                parent.rect(digitalPoint.getX() + xTrans, digitalPoint.getZ() + yTrans, pointWidth, pointHeight);
            }
        }
    }
    
    public void setWaterHeight(float waterHeight) {
        this.waterHeight = waterHeight - accumulateNumber;
    }
    
    public float getWaterHeight() {
        return waterHeight;
    }
    
    public Point3d getCenterPoint() {
        return centerPoint;
    }

    public void setCenterPoint(Point3d centerPoint) {
        this.centerPoint = centerPoint;
        this.setSize(viewInfo.getWidth(), viewInfo.getHeight());
    }

    public void setSize(float width, float height) {
        viewInfo = setSize(centerPoint, width, height);
    }
    
    private Rectangle setSize(Point3d centerPoint, float width, float height) {
        return new Rectangle(
                new Point3d(this.centerPoint.getX() - (width / 2), 
                        this.centerPoint.getY() - (height / 2),
                        this.centerPoint.getZ()), 
                width, height);
    }
    
    public float getWidth() {
        return viewInfo.getWidth();
    }
    
    public float getHeight() {
        return viewInfo.getHeight();
    }
    
}
